import win32com
import win32com.client
import pythoncom

############################################### INCA ##################################################
class IncaControl:
    """利用INCA接口控制INCA或读取INCA数据"""

    def init(self, expinit=True):
        pythoncom.CoInitialize()
        self.inca = win32com.client.Dispatch('inca.inca')
        if expinit: self.exp_init()

    def __del__(self):
        pythoncom.CoUninitialize()

    def exp_init(self):
        self.exp = self.inca.GetOpenedExperiment()
        assert self.exp, '无法连接到INCA实验环境'
        self.device = {}
        for dev in self.exp.GetAllDevices():  # 返回源数据集,如CCP: 1
            self.device[dev.GetName()] = dev

    def a2l_init(self, device_name='CCP: 1'):
        self.a2l = self.device[device_name].GetASAP2Module()
        self._a2ldevice = device_name

    reset_cal_name = []
    reset_cal_dict = {}

    def _getelement_m(self, name):
        a = self.exp.GetMeasureElement(name)
        assert a, '找不到该测量量:"%s"' % name
        return a

    def _getelement_c(self, name):
        a = self.exp.GetCalibrationElement(name)
        assert a, '找不到该标定量:"%s"' % name
        return a

    def mget(self, name):  # 返回测量量物理值
        """
        读取测量量的物理值

        name: 变量名称 字符串
        返回 浮点数
        """
        return self._getelement_m(name).GetValue().GetDoublePhysValue()

    def cget(self, name):  # 返回标定单参物理值(包括RAMCal数据)
        """
        读取标定单参的物理值(包括RAMCal数据)

        name: 变量名称 字符串
        返回 浮点数
        """
        return self._getelement_c(name).GetValue().GetDoublePhysValue()

    def mget_d(self, name):  # 返回测量量机器值
        """
        读取测量量机器值

        name: 变量名称 字符串
        返回 浮点数
        """
        return self._getelement_m(name).GetValue().GetImplValue()

    def mget_d_int(self, name):  # 返回测量量机器值
        """
        读取测量量机器值

        name: 变量名称 字符串
        返回 整数
        """
        return int(self._getelement_m(name).GetValue().GetImplValue())

    def cget_d(self, name):  # 返回标定单参机器值(包括RAMCal数据)
        """
        读取标定单参机器值(包括RAMCal数据)

        name: 变量名称 字符串
        返回 浮点数
        """
        return self._getelement_c(name).GetValue().GetImplValue()

    def cget_d_int(self, name):  # 返回标定单参机器值(包括RAMCal数据)(int)
        """
        读取标定单参机器值(包括RAMCal数据)

        name: 变量名称 字符串
        返回 整数
        """
        return self._getelement_c(name).GetValue().GetLongImplValue()

    def cset(self, name, value):  # 设置标定单参物理值(包括RAMCal数据)
        """
        设置标定单参物理值

        name: 变量名称 字符串
        value: 设定的数值 整数或浮点数
        返回 设置操作成功与否 布尔值
        """
        scalar = self._getelement_c(name).GetValue()
        assert scalar.IsScalar(), '标定量：' + name + '  不是单参'
        assert not scalar.IsWriteProtected(), '标定量：' + name + '  无法被标定'  # 可能不是在线标定数据
        return scalar.SetDoublePhysValue(value)

    def tabget(self, name):  # 读表格，返回数组，包含表格数据、X轴数据、Y轴数据
        """
        读取表格物理值

        如果读取的表格是一维表，则返回嵌套了2个一维列表的列表，嵌套的第1个列表存储了表格数据，嵌套的第2个列表存储了轴的数据
        如果读取的表格是二维表，则返回嵌套了3个列表的列表，嵌套的列表分别为保存表格数据的2维列表，保存X轴数据的1维列表，保存Y轴数据的1维列表
        其中表格2维数据结构是:[ [第1列], [第2列], [第3列], [第4列] ]  ，即索引方式是matrix_list[x][y]

        name: 变量名称 字符串
        返回 列表
        """
        tab = self._getelement_c(name).GetValue()
        if tab.IsTwoDTable():
            matrix = tab.GetValue().GetDoublePhysValue()
            matrix_list = []
            for tup in matrix:
                matrix_list.append(list(tup))  # 转为列表
            return [matrix_list, list(tab.GetXDistribution().GetDoublePhysValue()),
                    list(tab.GetYDistribution().GetDoublePhysValue())]
            # matrix_list : [ [第1列(x0)], [第2列(x1)], [第3列(x2)], [第4列(x3)] ]  matrix_list[x][y]
        elif tab.IsOneDTable():
            return [list(tab.GetValue().GetDoublePhysValue()), list(tab.GetDistribution().GetDoublePhysValue())]

    def tabget_d_int(self, name):  # 读表格，返回数组，包含表格数据、X轴数据、Y轴数据 (机器值整数)
        """
        读取表格物理值机器值

        与tabget()的不同之处在于存储的数据是整数机器值而非浮点数物理值

        name: 变量名称 字符串
        返回 列表
        """
        tab = self._getelement_c(name).GetValue()
        if tab.IsTwoDTable():
            matrix = tab.GetValue().GetImplValue()
            matrix_int = []
            for tup in matrix:
                matrix_int.append(list(int(a) for a in tup))
            return [matrix_int, list(int(a) for a in tab.GetXDistribution().GetImplValue()),
                    list(int(a) for a in tab.GetYDistribution().GetImplValue())]
        elif tab.IsOneDTable():
            return [list(int(a) for a in tab.GetValue().GetImplValue()),
                    list(int(a) for a in tab.GetDistribution().GetImplValue())]

    def reset_read(self, name):  # 读表格，返回数组，包含表格对象、表格数据、X轴数据、Y轴数据 # 增加读单参标定量
        """
        读取标定量对象物理值

        返回列表，2维表包含表格对象、表格数据、X轴数据、Y轴数据，1维表包含表格对象、表格数据、轴数据，单参包含标定量对象、标定数值

        name: 变量名称 字符串
        返回 列表
        """
        cal = self._getelement_c(name).GetValue()
        if cal.IsTwoDTable():
            return [cal, cal.GetValue().GetDoublePhysValue(), cal.GetXDistribution().GetDoublePhysValue(),
                    cal.GetYDistribution().GetDoublePhysValue()]
        elif cal.IsOneDTable():
            return [cal, cal.GetValue().GetDoublePhysValue(), cal.GetDistribution().GetDoublePhysValue()]
        else:
            return [cal, cal.GetDoublePhysValue()]

    def reset(self, obj, name=''):  # 重置标定量，输入标定量对象数组或标定量名称字符串
        """
        重置标定量

        将标定量的数据回滚到之前的某一状态

        obj: 通过reset_read()得到的列表 或 字符串名称（需要reset_cal_name里存在的）
        name: 标定量名称，不用填写
        返回 操作成功与否 布尔值
        """
        if isinstance(obj, str):
            name = obj
            obj = self.reset_cal_dict[obj]
        cal = obj[0]
        try:
            if cal.IsTwoDTable():
                a = cal.GetValue().SetDoublePhysValue(obj[1])
                b = cal.GetXDistribution().SetDoublePhysValue(obj[2])
                c = cal.GetYDistribution().SetDoublePhysValue(obj[3])
                rslt = a and b and c
            elif cal.IsOneDTable():
                a = cal.GetValue().SetDoublePhysValue(obj[1])
                b = cal.SetDistribution().SetDoublePhysValue(obj[2])
                rslt = a and b
            else:
                a = cal.SetDoublePhysValue(obj[1])
                rslt = a
            return bool(rslt)
        except:
            print('重置标定量:"%s"失败 ！！' % name)
            return False

    def all_reset_read(self):
        """
        读取reset_cal_name列表中的所有标定量数据并存放在reset_cal_dict里
        """
        for cal in self.reset_cal_name:
            self.reset_cal_dict[cal] = self.reset_read(cal)

    def all_reset(self):
        """
        将reset_cal_dict中的所有标定量数据回滚
        """
        for name, obj in self.reset_cal_dict.items():
            self.reset(obj, name)

    def tabset(self, name, value=None, xaxis_list=None, yaxis_list=None):  # 设置表格，输入单个量或是数组
        """
        设置表格物理值数据

        更改表格数据时参数可以是列表，也可以是数字，更改轴时参数必须是列表类型
        传入列表时，列表的大小应该与表格/轴的大小相同

        name: 变量名称 字符串
        value: 表格数据，可以是对应的1维or2维列表，也可以是一个整数/浮点数
        xaxis_list: X轴数据 列表
        yaxis_list: Y轴数据 列表
        返回 操作成功与否 布尔值
        """
        tab = self._getelement_c(name).GetValue()
        assert tab.IsCalibrationDataItem(), name + '  不是标定量'
        tab_value = tab.GetValue()
        assert (not tab_value.IsWriteProtected()) or (value == None), name + '  无法被标定'  # 可能不是在线标定数据
        input_way = 1 if isinstance(value, int) or isinstance(value, float) else 0
        vr, xr, yr = True, True, True
        if tab.IsTwoDTable():
            tab_xaxis = tab.GetXDistribution()
            tab_yaxis = tab.GetYDistribution()
            if input_way:
                x_size = tab_xaxis.GetSize()
                y_size = tab_yaxis.GetSize()
                value = [[value] * y_size] * x_size
            if yaxis_list:
                yr = tab_yaxis.SetDoublePhysValue(yaxis_list)
        else:
            tab_xaxis = tab.GetDistribution()
            if input_way == 1:
                x_size = tab_xaxis.GetSize()
                value = [value] * x_size
        if xaxis_list:
            xr = tab_xaxis.SetDoublePhysValue(xaxis_list)
        if value != None:
            vr = tab_value.SetDoublePhysValue(value)
        return bool(vr and xr and yr)

    def GetIdxFromAix(self, Axis, Vin):
        Nx = len(Axis)
        Vnew = Vin
        if Vnew <= Axis[0]:
            idx = 0
            Vnew = Axis[0]
        elif Vnew > Axis[Nx - 1]:
            idx = Nx - 2
            Vnew = Axis[Nx - 1]
        else:
            idx = 0
            for i in range(Nx):
                if Vnew > Axis[i]:
                    idx = i
                else:
                    break
        return Vnew, idx

    def get_idx_from_value(self, val_array, vin):
        for i in range(len(val_array) - 1):
            if (val_array[i] <= vin <= val_array[i + 1]) or (val_array[i] >= vin >= val_array[i + 1]):
                return i
        assert False, ' 反查表格时数据超出范围 '

    def lookup_tab(self, tab_name, x_value, y_value=0.0):
        """
        查表函数

        可以查1维或2维表格，利用物理值查表

        tab_name: 表格名称 字符串
        x_value: X轴数值 整数/浮点数
        y_value: Y轴数值（查1维表时不需要） 整数/浮点数
        返回 查表结果 浮点数
        """
        tab = self._getelement_c(tab_name).GetValue()
        if tab.IsOneDTable():
            val_array = tab.GetValue().GetDoublePhysValue()
            x_array = tab.GetDistribution().GetDoublePhysValue()
            x_value, x1 = self.GetIdxFromAix(x_array, x_value)
            x2 = x1 + 1
            ratio_x = (x_value - x_array[x1]) / (x_array[x2] - x_array[x1])
            z_value = val_array[x1] + (val_array[x2] - val_array[x1]) * ratio_x
            return z_value
        elif tab.IsTwoDTable():
            val_matrix = tab.GetValue().GetDoublePhysValue()
            x_array = tab.GetXDistribution().GetDoublePhysValue()
            y_array = tab.GetYDistribution().GetDoublePhysValue()
            x_value, x1 = self.GetIdxFromAix(x_array, x_value)
            x2 = x1 + 1
            y_value, y1 = self.GetIdxFromAix(y_array, y_value)
            y2 = y1 + 1

            ratio_y = (y_value - y_array[y1]) / (y_array[y2] - y_array[y1])
            ratio_x = (x_value - x_array[x1]) / (x_array[x2] - x_array[x1])
            dv_x1 = val_matrix[x1][y2] - val_matrix[x1][y1]
            temp1 = val_matrix[x1][y1] + dv_x1 * ratio_y
            dv_x2 = val_matrix[x2][y2] - val_matrix[x2][y1]
            temp2 = val_matrix[x2][y1] + dv_x2 * ratio_y
            z_value = temp1 + (temp2 - temp1) * ratio_x
            return z_value
        else:
            assert False, '这不是一个表格：' + tab_name

    def inverse_lookup_tab(self, tab_name, value):
        """
        反查表格，只能反查一维表，利用物理值查表

        tab_name: 表格名称 字符串
        value: 数值 整数/浮点数
        返回 查表结果 浮点数
        """
        tab = self._getelement_c(tab_name).GetValue()
        if tab.IsOneDTable():
            val_array = tab.GetValue().GetDoublePhysValue()
            x_array = tab.GetDistribution().GetDoublePhysValue()
            z1 = self.get_idx_from_value(val_array, value)
            z2 = z1 + 1
            ratio_z = (value - val_array[z1]) / (val_array[z2] - val_array[z1])
            x_value = x_array[z1] + (x_array[z2] - x_array[z1]) * ratio_z
            return x_value
        else:
            assert False, '只有一维表才能反查'

    def inverse_lookup_tab_for_temp(self, tab_name, value):
        """反查一维温度常数表"""
        tab = self._getelement_c(tab_name).GetValue()
        if tab.IsOneDTable():
            val_array = tab.GetValue().GetDoublePhysValue()
            x_array = tab.GetDistribution().GetDoublePhysValue()
            val_array_list = list(val_array)
            for i in range(len(val_array_list)):  # 无符号数变有符号数
                if int(round(val_array_list[i])) > 127: val_array_list[i] -= 256
            z1 = self.get_idx_from_value(val_array_list, value)
            z2 = z1 + 1
            ratio_z = (value - val_array_list[z1]) / (val_array_list[z2] - val_array_list[z1])
            x_value = x_array[z1] + (x_array[z2] - x_array[z1]) * ratio_z
            return x_value
        else:
            assert False, '只有一维表才能反查'

    def element_openview(self, *name, findin_RAMCal=False, skip_error=False):
        """
        将变量添加到INCA实验环境

        可以同时传入多个变量名称，且标定量与测量量都可以,如element_openview('MAPHPa','K_VVT_Select','EGRFlow',skip_error=True)

        skip_error不会忽略所有异常，只会跳过如’实验环境找不到变量‘这样的错误，‘系统不支持RamCal功能’这样的异常不会跳过
        name: 变量名称 字符串
        findin_RAMCal: 添加RAMCal变量 布尔值
        skip_error: 跳过错误 布尔值
        """
        if findin_RAMCal:
            for dev_name in self.device:
                if 'RAMCal' in dev_name:
                    ramcal_device = self.device[dev_name]
                    break
            else:
                assert False, '系统不支持RamCal功能'

        for name_temp in name:
            operate_rslt = True
            if findin_RAMCal:
                element = self.exp.GetExperimentElementInDevice(name_temp, ramcal_device)
                if element:
                    operate_rslt = element.OpenView()
            else:
                element = self.exp.GetExperimentElement(name_temp)
                if element:
                    operate_rslt = element.OpenView()

            assert element or skip_error, '找不到该变量:' + name_temp
            assert operate_rslt or skip_error, '添加变量到实验环境时操作失败' + name_temp
            temp = '成功' if operate_rslt and element else '失败 !!!!! '
            print('openview >> "%s" >> %s' % (name_temp, temp))

    def set_element_samplerate(self, *name, samplerate_str='Task10ms', skip_error=False):
        """
        设置测量量的采样率

        skip_error不会忽略所有异常，只会跳过如’实验环境找不到变量‘这样的错误，‘系统不存在该采样率’这样的异常不会跳过
        name: 测量量名称 字符串
        samplerate_str: 采样率名称,如'Task10ms',''Task100ms'' 字符串
        skip_error: 跳过错误 布尔值
        """
        for dev in self.device.values():
            if dev.IsAcquisitionRateAvailable(samplerate_str): break
        else:
            assert False, 'Function set_element_samplerate():系统不存在该采样率：' + samplerate_str
        for name_temp in name:
            var = self.exp.GetExperimentElement(name_temp)
            if var and var.IsMeasureElement():
                rslt = var.GetValue().SetCurrentAcquisitionRate(samplerate_str)
                assert rslt, '设置采样率失败(请先停止测量)：' + name_temp + '>>' + samplerate_str
            else:
                if not skip_error:
                    print(name_temp, '不是测量量')
    @staticmethod
    def getaccu_fromgain(gain):
        if gain == 0:
            accu = 'error'
        elif round(gain, 8) == int(gain):
            accu = '1/%d' % (int(gain))
        elif round(1 / gain, 8) == int(1 / gain):
            accu = str(int(1 / gain))
        else:
            accu = str(round(1 / gain, 8))
        return accu

    def geta2ldata_fromcompu(self, compu):
        try:
            conver = compu.GetConversionType()
        except:
            return '!error', '!error!'
        if conver == 'Linear':
            c, b = compu.GetParameterValues()
            if c == 0:
                accu = self.getaccu_fromgain(b)
            else:
                accu = 'imp = phy*{0}+{1}'.format(b, c)
        elif conver == 'Identity':
            accu = '1'
        elif conver == 'TAB_VERB':
            accu = '枚举'
        elif conver == 'Moebius':
            a, b, c, d, e, f = compu.FormulaCoefficients()
            if a == 0 and b != 0 and c == 0 and d == 0 and e == 0 and f != 0:
                gain = b / f
                accu = self.getaccu_fromgain(gain)
            else:
                accu = 'imp=({0}*phy^2+{1}*phy+{2})/({3}*phy^2+{4}*pyh+{5})'.format(a, b, c, d, e, f)
        else:
            accu = 'error'
        unit = compu.GetUnit()
        return unit, accu

    def geta2ldata(self, elema2l):
        compu = elema2l.GetCompuMethod()
        unit, accu = self.geta2ldata_fromcompu(compu)
        lobound = elema2l.GetLowerBound()
        lobound = str(int(lobound)) if int(lobound) == lobound else str(lobound)
        hibound = elema2l.GetUpperBound()
        hibound = str(int(hibound)) if int(hibound) == hibound else str(hibound)
        addr = elema2l.GetAddressAsHexString()
        type = elema2l.GetDataType()
        comment = elema2l.GetComment()
        return unit, lobound, hibound, accu, addr, type, comment